/**
 * Name: Ofri Keidar
 * ID: 302933981
 * Group: 04
 * Exercise: ex42
 * Linux Distribution:Ubuntu
 * Version: 12.04.2 LTS, Precise Pangolin
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/sem.h>

#define ARGS_NUM 3 // number of needed arguments to main function
#define SOCK_FAMILY_PROTOCOL 0 // protocol used for socket's family

// message details
#define MSG_MAX_SIZE 160*sizeof(char) // maximal message size
#define CLOSE_CONNECTION_MSG "Close" // message from user to indicate to close connection

// return values of boolean functions
#define TRUE 1
#define FALSE 0

int setupConnection(char* ip, unsigned short port);
void* listenToServer(void *arguments);
size_t readServerMessage(char msgBuffer[MSG_MAX_SIZE], int serverSockFd, int numBytesToRead);
void closeSocket(int socketFd);
void listenToKeyboard(int serverSockFd);
void getUserInput(char message[MSG_MAX_SIZE]);
void writeMsgToServer(char message[MSG_MAX_SIZE], int serverSockFd);
int checkIfCloseSocket(char message[MSG_MAX_SIZE], size_t bytesRead);
int isCloseMessage(char message[MSG_MAX_SIZE]);

/**
 * Gets server's ip and port and creates a TCP socket to connect to server.
 * Uses a new thread to read server's messages and to print them to the screen,
 * and on main thread reads user's messages (from keyboard) and sends them to server
 */
int main(int argc, char *argv[]) {

	char* ip; // server's ip
	unsigned short port; // server's port
	int serverSockFd; // fd of socket to server
	pthread_t newThreadId; // identifier for new thread
	void* arguments[1]; // arguments array for client handling

	// validate number of arguments
	if (ARGS_NUM != argc) {

		// display error message and exit program
		printf("Wrong number of arguments!\n");
		exit(1);
	}

	// extract ip and port from arguments
	ip = argv[1];
	port = atoi(argv[2]);

	// setup connection to server with given ip and port
	serverSockFd = setupConnection(ip, port);

	// start new thread to listen to server
	((int*)arguments)[0] =  serverSockFd; // set socket fd as argument
	if (pthread_create(&newThreadId, NULL, listenToServer, arguments)) {

		// display error message and exit program
		perror("pthread_create()");
		exit(1);
	}

	// listen to keyboard for user's input
	listenToKeyboard(serverSockFd);

	return EXIT_SUCCESS;
}

/**
 * Creates an IPv4 socket using TCP and connects it
 * to given port and ip of server.
 * If succeeds, returns fd of the socket that was created,
 * otherwise, prints error message and exists program
 */
int setupConnection(char* ip, unsigned short port) {

	struct sockaddr_in serverAddrres; // holds server's address
	int socketFd; // holds id of socket to be created

	// create IPv4 TCP socket
	if ((socketFd = socket(AF_INET, SOCK_STREAM, SOCK_FAMILY_PROTOCOL)) < 0) {

		// display error message and exit program
		perror("socket()");
		exit(1);
	}

	// initialize server address using given port
	serverAddrres.sin_family = AF_INET;
	serverAddrres.sin_port = htons(port);
    if (inet_pton(AF_INET, ip, &serverAddrres.sin_addr) <= 0) {

		// display error message and exit program
		perror("inet_pton()");
		exit(1);
    }

	// connect to server
    if(connect(socketFd, (struct sockaddr *)&serverAddrres, sizeof(serverAddrres)) < 0) {

		// display error message and exit program
		perror("connect()");
		exit(1);
    }

	// return fd of created socket
	return socketFd;
}

/**
 * Listens to server- reads content from socket and prints it
 * to the screen (should run on a new thread).
 * Function must get as parameter the fd of server's socket.
 * If socket should be closed (did not read any data or server
 * send message indicating to close the socket), closes the socket
 * and exits program
 */
void* listenToServer(void *arguments) {

	// extract server's socket fd from arguments
	int serverSockFd = ((int*)arguments)[0];

	// initialize buffer to hold server's messages
	char message[MSG_MAX_SIZE];

	// initialize flag for keep listening to server
	int keepListening = TRUE;

	// counts how many bytes read from server
	size_t bytesRead;

	// keep listening to server until connection is closed
	while (keepListening) {

		// read server's message
		bytesRead = readServerMessage(message, serverSockFd, sizeof(message)-1);

		// check if should close the connection
		if (checkIfCloseSocket(message, bytesRead)) {

			// done listening, no return value
			closeSocket(serverSockFd);

			// end program
			exit(1);
		}

		// print message from server
		printf("%s\n", message);
	}

	// no return value
	return NULL;
}

/**
 * Reads message from given server's socket, and writes message
 * to given buffer (reads number of bytes that is indicated in parameter).
 * If had errors reading message, displays error message (on client)
 * and exists program.
 * Returns how many bytes were read
 */
size_t readServerMessage(char msgBuffer[MSG_MAX_SIZE], int serverSockFd,
		int numBytesToRead) {

	size_t bytesRead; // counts how many bytes read from server

	// read server's message
	if ((bytesRead = read(serverSockFd, msgBuffer, numBytesToRead)) == -1) {

		// display error message and exit program
		perror("read()");
		exit(1);
	}

	// terminate string
	msgBuffer[bytesRead] = 0;

	// return number of read bytes
	return bytesRead;
}

/**
 * Gets fd of server's socket and closes it.
 * If had errors closing the socket, displays error message (on client)
 * and exists program
 */
void closeSocket(int socketFd) {

	// close socket to server
	if (close(socketFd) == -1) {

		// display error message and exit program
		perror("close()");
		exit(1);
	}
}

/**
 * Listens to keyboard- reads user's input from keyboard
 * and sends it to server (should run on main thread).
 * Function gets as parameter the fd of server's socket
 */
void listenToKeyboard(int serverSockFd) {

	// initialize buffer to hold user's messages
	char message[MSG_MAX_SIZE];

	// initialize flag for keep listening to keyboard
	int keepListening = TRUE;

	// keep listening to user until user wishes to end chat
	while (keepListening) {

		// get user's input
		getUserInput(message);

		// write message to server
		writeMsgToServer(message, serverSockFd);

		// check if wishes to end chat
		if (isCloseMessage(message)) {

			// done listening to keyboard
			keepListening = FALSE;
		}
	}
}

/**
 * Reads user's input from keyboard into given message buffer
 */
void getUserInput(char message[MSG_MAX_SIZE]) {

	char currChar; // current char being read from keyboard
	int nextCharIndex = 0; // index of next char in message string

	// read input from keyboard char by char until new line
	while((currChar = getchar()) != '\n') {

		// add current char to message
		message[nextCharIndex] = currChar;

		// increment index of next char to append
		++nextCharIndex;
	}

	// terminate string
	message[nextCharIndex] = 0;
}

/**
 * Gets server's socket fd and writes to this socket given message.
 * If had problems writing the message, displays error message (on
 * client) and exists program
 */
void writeMsgToServer(char message[MSG_MAX_SIZE], int serverSockFd) {

	// send message to server
	if (write(serverSockFd, message, strlen(message)) < strlen(message)) {

		// display error message and exit program
		perror("write()");
		exit(1);
	}
}

/**
 * Checks if socket connecting to server should be closed.
 * Socket should be closed if message read from server instructs
 * closing the socket, or if no bytes were read on last reading.
 * Function gets last message to be read and number of bytes from
 * last reading
 */
int checkIfCloseSocket(char message[MSG_MAX_SIZE], size_t bytesRead) {

	// check values and return result
	return isCloseMessage(message) || (!bytesRead);
}

/**
 * Checks if given message indicates if user wishes
 * to end the connection.
 * If so, returns TRUE, otherwise FALSE
 */
int isCloseMessage(char message[MSG_MAX_SIZE]) {

	// compare messages and return result
	return strcmp(message, CLOSE_CONNECTION_MSG) == 0;
}
